#include "stdafx.h"
#include "TreePlanter.h"

#include "HeightMap.h"
#include "../../Framework/Toolkit.h"

using namespace Framework;

namespace MyGame {

TreePlanter::TreePlanter(Game* game) : DrawableGameComponent(game) {
    this->lodRange = 100;
    this->minHeight = 0;
    this->treeLine1Height = 0;
    this->treeLine2Height = 0;
    this->treeLine3Height = 0;
    this->visibleTrees = NULL;
    this->visibilityRange = 100;
    this->treeModels = new LPCSTR[6];
    this->treeModels[0] = "tree2_LOD.x";
    this->treeModels[1] = "tree3_LOD.x";
    this->treeModels[2] = "tree4_LOD.x";
    this->treeModels[3] = "tree5_LOD.x";
    this->treeModels[4] = "tree1_LOD.x";
    this->treeModels[5] = "tree6_LOD.x";
    this->treeModelsLOD = new LPCSTR[6];
    this->treeModelsLOD[0] = "tree2_LOD2.x";
    this->treeModelsLOD[1] = "tree3_LOD2.x";
    this->treeModelsLOD[2] = "tree4_LOD2.x";
    this->treeModelsLOD[3] = "tree5_LOD2.x";
    this->treeModelsLOD[4] = "tree1_LOD2.x";
    this->treeModelsLOD[5] = "tree6_LOD2.x";
}

TreePlanter::~TreePlanter(void) {
}

FLOAT TreePlanter::GetLodRange() const {
    return this->lodRange;
}

void TreePlanter::SetLodRange(FLOAT val) {
    this->lodRange = val;
}

FLOAT TreePlanter::GetVisibilityRange() const {
    return this->visibilityRange;
}

void TreePlanter::SetVisibilityRange(FLOAT val) {
    this->visibilityRange = val;
}

void TreePlanter::SetTerrain(Terrain* val) {
    this->terrain = val;
}

void TreePlanter::SetTreeLineHeights(FLOAT minHeight, FLOAT treeLine1Height, FLOAT treeLine2Height, FLOAT treeLine3Height) {
    this->minHeight = minHeight;
    this->treeLine1Height = treeLine1Height;
    this->treeLine2Height = treeLine2Height;
    this->treeLine3Height = treeLine3Height;
}

void TreePlanter::Draw(FLOAT timeDelta) {
    __super::Draw(timeDelta);
    for (vector<TreeBase<vector<Tree*>*>*>::iterator it = this->visibleTrees->begin();
            it != this->visibleTrees->end();
            ++it) {
        FrustumCullingType frustumCullingType = this->GetGame()->GetCamera()->SphereInRadius((*it)->GetBoundingSphere(), this->lodRange);
        for (vector<Tree*>::iterator treeIt = (*it)->GetItem()->begin(); treeIt != (*it)->GetItem()->end(); ++treeIt) {
            if (frustumCullingType != FRUSTUMCULLINGTYPE_NONE) {
                (*treeIt)->Draw(timeDelta);
            } else {
                (*treeIt)->DrawLodModel(timeDelta);
            }
        }
    }
}

void TreePlanter::Initialize() {
    HeightMap* heightMap = this->terrain->GetHeightMap();
    // TODO: Parameterize
    int treeNodes = (int)sqrt((FLOAT)heightMap->GetWidth() * (FLOAT)heightMap->GetLength());
    int mapWidth = heightMap->GetWidth();
    int mapLength = heightMap->GetLength();
    float halfWidth = ((float)mapWidth - 1.0f) / 2.0f;
    float halfLength = ((float)mapLength - 1.0f) / 2.0f;

    d3d::BoundingBox* boundingBox = this->terrain->GetMesh()->GetBoundingBox();
    this->octree = new Octree<vector<Tree*>*>(boundingBox, 1, NULL);
    this->InitializeOctree(this->octree);

    Quadtree<TerrainPart*>* terrainParts = this->terrain->GetTerrainParts();

    for (int i = 0; i < treeNodes; ++i) {
        float nodeX = (FLOAT)Toolkit::RandomFloat(-halfWidth, mapWidth - 1 - halfWidth);
        float nodeZ = (FLOAT)Toolkit::RandomFloat(-halfLength, mapLength - 1 - halfLength);
        // TODO: Parameterize
        float radius = 10;
        // TODO: Parameterize
        int amount = 100;

        for (int j = 0; j < amount; ++j) {
            float angle = (FLOAT)Toolkit::RandomFloat(0, 2 * D3DX_PI);
            float distance = (FLOAT)Toolkit::RandomFloat(0, radius);
            float treeX = nodeX + sin(angle) * distance;
            float treeZ = nodeZ + cos(angle) * distance;
            BOOL hit;
            FLOAT distenceToMesh;
            const TreeBase<TerrainPart*>* terrainPart = terrainParts->FindChild(D3DXVECTOR3(treeX, 0, treeZ));
            if (terrainPart == NULL) {
                continue;
            }

            D3DXIntersect(terrainPart->GetItem()->GetMesh(), &D3DXVECTOR3(treeX, 0, treeZ), &D3DXVECTOR3(0, 1, 0), &hit, NULL, NULL, NULL, &distenceToMesh, NULL, NULL);

            if (hit == TRUE && (distenceToMesh > this->minHeight && distenceToMesh < this->treeLine3Height)) {
                Tree* tree = new Tree(this->GetGame());
                tree->SetScale(D3DXVECTOR3(0.3f, 0.3f, 0.3f));
                tree->SetPosition(D3DXVECTOR3(treeX, distenceToMesh, treeZ));
                tree->SetRotation(D3DXVECTOR3(0, (FLOAT)Toolkit::RandomFloat(0, 2 * D3DX_PI), 0));

                int treeModel = 0;

                if (distenceToMesh < this->treeLine1Height) {
                    treeModel = Toolkit::RandomInt(0, 1);
                } else if (distenceToMesh < this->treeLine2Height) {
                    treeModel = Toolkit::RandomInt(2, 3);
                } else if (distenceToMesh < this->treeLine3Height) {
                    treeModel = Toolkit::RandomInt(4, 5);
                } else {
                    treeModel = Toolkit::RandomInt(0, 5);
                }

                tree->SetMeshFile(this->treeModels[treeModel]);
                tree->SetMeshFileLOD(this->treeModelsLOD[treeModel]);
                this->Insert(this->octree, tree, tree->GetPosition());
            }
        }
    }

    for (vector<Tree*>::iterator it = this->octree->GetItem()->begin(); it != this->octree->GetItem()->end(); ++it) {
        (*it)->Initialize();
    }
}

void TreePlanter::InitializeOctree(Octree<vector<Tree*>*>* octree) {
    octree->SetItem(new vector<Tree*>);

    for (vector<TreeBase<vector<Tree*>*>*>::iterator it = octree->GetChildren()->begin(); it != octree->GetChildren()->end(); ++it) {
        this->InitializeOctree((Octree<vector<Tree*>*>*)*it);
    }
}

void TreePlanter::Insert(Octree<vector<Tree*>*>* octree, Tree* tree, D3DXVECTOR3 position) {
    if (octree->GetBoundingBox()->isPointInside(position)) {
        octree->GetItem()->push_back(tree);

        for (vector<TreeBase<vector<Tree*>*>*>::iterator it = octree->GetChildren()->begin(); it != octree->GetChildren()->end(); ++it) {
            this->Insert((Octree<vector<Tree*>*>*)*it, tree, position);
        }
    }
}

void TreePlanter::LoadContent() {
    for (vector<Tree*>::iterator it = this->octree->GetItem()->begin(); it != this->octree->GetItem()->end(); ++it) {
        (*it)->LoadContent();
    }
}

void TreePlanter::UpdateVisibleParts() {
    if (this->visibleTrees != NULL) {
        d3d::Delete(this->visibleTrees);
    }

    this->GetGame()->GetCamera()->SetVisibilityRange(this->visibilityRange);
    this->visibleTrees = this->octree->FindVisible(this->GetGame()->GetCamera());
}

}
